use axum::{
    extract::{Json as ExtractJson, Path as ExtractPath, State},
    http::StatusCode,
    response::IntoResponse,
};
use serde::Serialize;
use std::sync::Arc;
use tracing::{debug, error, trace, warn};

use bc_base::{TransactionId, transaction::Transaction};
use bc_manager::LocalTransaction;

use crate::AppState;

#[derive(Debug, Serialize)]
struct TransactionInfo {
    sender: String,
    receiver: String,
    amount: f64,
    timestamp: String,
    status: String,
}

// curl -X GET http://localhost:3000/api/transaction/9ddfd2ae61cbc5e74c0b1547c52a6363652233e33cd4d6527f7b65cbd7ef6c6f

pub(crate) async fn query_transaction(
    State(state): State<Arc<AppState>>,
    ExtractPath(transaction_id): ExtractPath<String>,
) -> impl IntoResponse {
    debug!("query_transaction. tx_id: {}", transaction_id);

    match TransactionId::from_hex_str(&transaction_id) {
        Ok(tx_id) => {
            let mng = state.bc_manager.lock().await;
            let r = mng.get_transaction(&tx_id).await;
            match r {
                Ok(tx) => {
                    let (tx, place) = match tx {
                        LocalTransaction::TransactionInMemPool(tx) => (tx, "mem-pool"),
                        LocalTransaction::TransactionInDb(tx) => (tx, "db"),
                    };
                    trace!(
                        "Query transaction: {}. tx(in {place}): {:?}",
                        &transaction_id, tx
                    );
                    (
                        StatusCode::OK,
                        ExtractJson(serde_json::json!(convert_to(&tx))),
                    )
                }
                Err(e) => {
                    warn!("[{}] Failed to query transaction.", e);
                    (
                        StatusCode::NOT_FOUND,
                        ExtractJson(serde_json::json!({"error": "TX not found"})),
                    )
                }
            }
        }

        Err(e) => {
            error!("[{:?}] Failed to query transaction.", e);
            (
                StatusCode::NOT_FOUND,
                ExtractJson(serde_json::json!({"error": "Invalid transaction ID"})),
            )
        }
    }
}

fn convert_to(raw_tx: &Transaction) -> TransactionInfo {
    let script_signature = raw_tx.get_input_script(0);
    let sender = bc_lib::transaction::get_transaction_sender(&script_signature);
    let sender = if let Some(sender) = sender {
        sender.to_string()
    } else {
        "null".to_string()
    };

    let script_pubkey = raw_tx.get_output_script(0);
    let receiver = bc_lib::transaction::get_transaction_receiver(&script_pubkey);
    let receiver = if let Some(receiver) = receiver {
        receiver.to_string()
    } else {
        "null".to_string()
    };

    // TODO: multi-output transaction
    let amount: f64 = raw_tx.get_output_value() as f64 / bc_base::constants::COIN as f64;
    let timestamp = raw_tx.get_timestamp();
    let timestamp = bc_util::seconds_to_local_time(timestamp);
    let status: String = "test".to_string();

    TransactionInfo {
        sender,
        receiver,
        amount,
        timestamp,
        status,
    }
}
